/*
 * Tigase Jabber/XMPP Server
 * Copyright (C) 2004-2012 "Artur Hefczyc" <artur.hefczyc@tigase.org>
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as published by
 * the Free Software Foundation, version 3 of the License.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program. Look for COPYING file in the top folder.
 * If not, see http://www.gnu.org/licenses/.
 *
 * $Rev: 2996 $
 * Last modified by $Author: wojtek $
 * $Date: 2012-08-21 06:29:57 +0800 (Tue, 21 Aug 2012) $
 */
package tigase.server.amp;

//~--- non-JDK imports --------------------------------------------------------

import tigase.disco.XMPPService;

import tigase.server.AbstractMessageReceiver;
import tigase.server.Packet;
import tigase.server.amp.action.Alert;
import tigase.server.amp.action.Drop;
import tigase.server.amp.action.Notify;
import tigase.server.amp.action.Store;
import tigase.server.amp.cond.Deliver;
import tigase.server.amp.cond.ExpireAt;
import tigase.server.amp.cond.MatchResource;

import tigase.xml.Element;

import tigase.xmpp.JID;

//~--- JDK imports ------------------------------------------------------------

import java.util.List;
import java.util.Map;
import java.util.Queue;
import java.util.concurrent.ConcurrentSkipListMap;
import java.util.logging.Level;
import java.util.logging.Logger;

//~--- classes ----------------------------------------------------------------

/**
 * Created: Apr 26, 2010 3:22:06 PM
 * 
 * @author <a href="mailto:artur.hefczyc@tigase.org">Artur Hefczyc</a>
 * @version $Rev: 2996 $
 */
public class AmpComponent extends AbstractMessageReceiver implements
		ActionResultsHandlerIfc {
	private static final Logger log = Logger.getLogger(AmpComponent.class.getName());
	private static final String AMP_NODE = "http://jabber.org/protocol/amp";
	private static final String AMP_XMLNS = AMP_NODE;
	private static final Element top_feature = new Element("feature",
			new String[] { "var" }, new String[] { AMP_NODE });

	// ~--- fields ---------------------------------------------------------------

	private Map<String, ActionIfc> actions = new ConcurrentSkipListMap<String, ActionIfc>();
	private Map<String, ConditionIfc> conditions =
			new ConcurrentSkipListMap<String, ConditionIfc>();

	// ~--- methods --------------------------------------------------------------

	/**
	 * Method description
	 * 
	 * 
	 * @param packet
	 * 
	 * @return
	 */
	@Override
	public boolean addOutPacket(Packet packet) {
		return super.addOutPacket(packet);
	}

	/**
	 * Method description
	 * 
	 * 
	 * @param packets
	 * 
	 * @return
	 */
	@Override
	public boolean addOutPackets(Queue<Packet> packets) {
		return super.addOutPackets(packets);
	}

	// ~--- get methods ----------------------------------------------------------

	/**
	 * Method description
	 * 
	 * 
	 * @param params
	 * 
	 * @return
	 */
	@Override
	public Map<String, Object> getDefaults(Map<String, Object> params) {
		Map<String, Object> defs = super.getDefaults(params);
		ActionIfc action = new Drop();

		actions.put(action.getName(), action);
		action = new tigase.server.amp.action.Error();
		actions.put(action.getName(), action);
		action = new Notify();
		actions.put(action.getName(), action);
		action = new tigase.server.amp.action.Deliver();
		actions.put(action.getName(), action);
		action = new Store();
		actions.put(action.getName(), action);
		action = new Alert();
		actions.put(action.getName(), action);

		ConditionIfc condition = new Deliver();

		conditions.put(condition.getName(), condition);
		condition = new ExpireAt();
		conditions.put(condition.getName(), condition);
		condition = new MatchResource();
		conditions.put(condition.getName(), condition);

		for (ActionIfc a : actions.values()) {
			Map<String, Object> d = a.getDefaults(params);

			if (d != null) {
				defs.putAll(d);
			}
		}

		// for (ConditionIfc c : conditions.values()) {
		// Map<String, Object> d = c.getDefaults(params);
		//
		// if (d != null) {
		// defs.putAll(d);
		// }
		// }
		return defs;
	}

	/**
	 * Method description
	 * 
	 * 
	 * @return
	 */
	@Override
	public String getDiscoCategoryType() {
		return "generic";
	}

	/**
	 * Method description
	 * 
	 * 
	 * @return
	 */
	@Override
	public String getDiscoDescription() {
		return "IM AMP Support";
	}

	/**
	 * Method description
	 * 
	 * 
	 * @param node
	 * @param jid
	 * @param from
	 * 
	 * @return
	 */
	@Override
	public Element getDiscoInfo(String node, JID jid, JID from) {
		Element query = super.getDiscoInfo(node, jid, from);

		if ((jid != null)
				&& (getName().equals(jid.getLocalpart()) || isLocalDomain(jid.toString()))
				&& (AMP_NODE.equals(node))) {
			if (query == null) {
				query = new Element("query");
				query.setXMLNS(XMPPService.INFO_XMLNS);
			}

			query.addChild(new Element("identity", new String[] { "name", "category", "type" },
					new String[] { getDiscoDescription(), "im", getDiscoCategoryType() }));
			query.addChild(top_feature);

			for (ActionIfc action : actions.values()) {
				query.addChild(new Element("feature", new String[] { "var" },
						new String[] { AMP_NODE + "?action=" + action.getName() }));
			}

			for (ConditionIfc cond : conditions.values()) {
				query.addChild(new Element("feature", new String[] { "var" },
						new String[] { AMP_NODE + "?condition=" + cond.getName() }));
			}

			// for (ProcessingThreads<ProcessorWorkerThread> proc_t :
			// processors.values()) {
			// Element[] discoFeatures =
			// proc_t.getWorkerThread().processor.supDiscoFeatures(null);
			//
			// if (discoFeatures != null) {
			// query.addChildren(Arrays.asList(discoFeatures));
			// } // end of if (discoFeatures != null)
			// }
		}

		if (log.isLoggable(Level.FINEST)) {
			log.finest("Found disco info: " + ((query != null) ? query.toString() : null));
		}

		return query;
	}

	// ~--- methods --------------------------------------------------------------

	/**
	 * Method description
	 * 
	 * 
	 * @param packet
	 */
	@Override
	public void processPacket(Packet packet) {
		if (log.isLoggable(Level.FINEST)) {
			log.finest("My packet: " + packet);
		} 

		ActionIfc def = null;

		if (packet.getAttribute(AmpFeatureIfc.OFFLINE) == null) {
			def = actions.get("deliver");
		} else {
			def = actions.get("store");
		}

		boolean exec_def = true;
		Element amp = packet.getElement().getChild("amp", AMP_XMLNS);

		if (amp != null) {
			List<Element> rules = amp.getChildren();

			if ((rules != null) && (rules.size() > 0)) {
				for (Element rule : rules) {
					if (matchCondition(packet, rule)) {
						exec_def = executeAction(packet, rule);

						break;
					}
				}
			} else {
				log.warning("AMP packet but empty rule-set! " + packet);
				// In case of such error, let's just drop the packet
				return;
			}
		} else {
			log.warning("Not an AMP packet! " + packet);
			// In case of such error, let's just drop the packet
			return;
		}

		if (exec_def) {
			if (log.isLoggable(Level.FINEST)) {
				log.finest("Executing default action: " + def.getName());
			}

			def.execute(packet, null);
		}
	}

	// ~--- set methods ----------------------------------------------------------

	/**
	 * Method description
	 * 
	 * 
	 * @param props
	 */
	@Override
	public void setProperties(Map<String, Object> props) {
		super.setProperties(props);

		if (props.size() == 1) {
			// If props.size() == 1, it means this is a single property update
			// and this component does not support single property change for the rest
			// of it's settings
			return;
		}

		for (ActionIfc a : actions.values()) {
			a.setProperties(props, this);
		}

		// for (ConditionIfc c : conditions.values()) {
		// c.setProperties(props, this);
		// }
	}

	// ~--- methods --------------------------------------------------------------

	private boolean executeAction(Packet packet, Element rule) {
		String act = rule.getAttribute(AmpFeatureIfc.ACTION_ATT);

		if (act != null) {
			ActionIfc action = actions.get(act);

			if (action != null) {
				boolean result = action.execute(packet, rule);

				if (log.isLoggable(Level.FINEST)) {
					log.finest("Matched action: " + action.getName() + ", result: " + result);
				}

				return result;
			} else {
				log.fine("No action found for act: " + act);
			}
		} else {
			log.fine("No actionset for rule: " + rule);
		}

		return true;
	}

	private boolean matchCondition(Packet packet, Element rule) {
		String cond = rule.getAttribute(AmpFeatureIfc.CONDITION_ATT);

		if (cond != null) {
			ConditionIfc condition = conditions.get(cond);

			if (condition != null) {
				boolean result = condition.match(packet, rule);;

				if (log.isLoggable(Level.FINEST)) {
					log.finest("Matched condition: " + condition.getName() + ", result: " + result);
				}

				return result;
			} else {
				log.fine("No condition found for cond: " + cond);
			}
		} else {
			log.fine("No condition set for rule: " + rule);
		}

		return false;
	}
}

// ~ Formatted in Sun Code Convention

// ~ Formatted by Jindent --- http://www.jindent.com
